package com.nulldev.util.web.YouTubeExtractor.shorts;

import static com.nulldev.util.web.YouTubeExtractor.util.YouTubeExtractorUtil.getYTString;
import static com.nulldev.util.web.YouTubeExtractor.util.YouTubeExtractorUtil.hasErrors;

import java.io.Reader;
import java.util.List;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.internal.backport.concurrency9.Maps;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpMethod;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.util.YTRequestObject;

public class YouTubeReelExtractor {

	private static final Logger log = LoggerFactory.getLogger();

	public static enum ReelType {
		ITEM_WATCH,
		WATCH_SEQUENCE
	}

	public static class ReelVideo {

		private final String videoID;
		private final String videoTitle;
		private final String videoAuthor;

		public ReelVideo(final String videoID, final String videoTitle, final String videoAuthor) {
			this.videoID = videoID;
			this.videoAuthor = videoAuthor;
			this.videoTitle = videoTitle;
		}

		public String getVideoID() {
			return this.videoID;
		}

		public String getVideoTitle() {
			return this.videoTitle;
		}

		public String getVideoAuthor() {
			return this.videoAuthor;
		}

		@Override
		public String toString() {
			return "ReelVideo[hash=" + MathUtil.toHex(super.hashCode()) + ",id=" + this.videoID + ",title=" + this.videoTitle + ",author=" + this.videoAuthor
					+ "]";
		}
	}

	public static class YouTubeReel {
		private final String continuation;
		private final List<ReelVideo> videos = FastLists.list(0);

		public YouTubeReel(final String continuation) {
			this.continuation = continuation;
		}

		public YouTubeReel(final String continuation, final ReelVideo video) {
			this.continuation = continuation;
			this.videos.add(video);
		}

		public boolean hasContinuation() {
			return this.continuation != null;
		}

		public String getContinuation() {
			return this.continuation;
		}

		public List<ReelVideo> getVideos() {
			return this.videos;
		}

		public boolean hasSingletonVideo() {
			return this.videos.size() == 1;
		}
	}

	public static YouTubeReel extract() throws Exception {
		return extract(ReelType.ITEM_WATCH, null);
	}

	public static YouTubeReel extract(final ReelType type, final String sequenceParams) throws Exception {
		Variables.requireNonNull(type, "type");
		if (type == ReelType.WATCH_SEQUENCE && (sequenceParams == null || sequenceParams.isEmpty()))
			throw new IllegalArgumentException("Reel type WATCH_SEQUENCE requires a sequenceParams.");
		String url = "https://www.youtube.com/youtubei/v1/reel/";
		switch (type) {
			case ITEM_WATCH: {
				url += "reel_item_watch?key=" + YouTubeExtractorConstants.API_KEY + "&prettyPrint=false";
				break;
			}
			case WATCH_SEQUENCE: {
				url += "reel_watch_sequence?key=" + YouTubeExtractorConstants.API_KEY + "&prettyPrint=false";
				break;
			}
			default:
				throw new UnsupportedOperationException("Unknown reel type: " + type);
		}
		final UniJsonValue data = YTRequestObject.makeYTRequestObject(Maps.of());
		switch (type) {
			case ITEM_WATCH:
				data.add("disablePlayerResponse", true);
				data.add("inputType", "REEL_WATCH_INPUT_TYPE_SEEDLESS");
				data.add("params", "CA8%3D");
				break;
			case WATCH_SEQUENCE:
				data.add("sequenceParams", sequenceParams);
				break;
			default:
				break;
		}
		final HttpResponse<Reader> r = HttpClient.getReader().url(url).create(HttpMethod.POST, data.toString());
		final UniJsonValue uv = UniJsonValue.parse(r.data());
		IOUtils.closeQuietly(r);
		if (hasErrors(uv)) {
			log.error("Failed to extract reel.");
			log.error(uv);
			return null;
		} else if (type == ReelType.ITEM_WATCH) {
			final String seqCont = uv.getString("sequenceContinuation");
			if (!uv.contains("replacementEndpoint")) {
				log.error("Failed to find replacementEndpoint!");
				return null;
			}
			final UniJsonValue replacementEndpoint = uv.get("replacementEndpoint");
			if (!replacementEndpoint.contains("reelWatchEndpoint")) {
				log.error("Failed to find reelWatchEndpoint!");
				return null;
			}
			final UniJsonValue reelWatchEndpoint = replacementEndpoint.get("reelWatchEndpoint");
			final String nextVideoId = reelWatchEndpoint.getString("videoId");
			final UniJsonValue overlay = reelWatchEndpoint.get("overlay");
			final UniJsonValue reelPlayerOverlayRenderer = overlay.get("reelPlayerOverlayRenderer");
			final UniJsonValue reelPlayerHeaderSupportedRenderers = reelPlayerOverlayRenderer.get("reelPlayerHeaderSupportedRenderers");
			final UniJsonValue reelPlayerHeaderRenderer = reelPlayerHeaderSupportedRenderers.get("reelPlayerHeaderRenderer");
			final ReelVideo video = new ReelVideo(nextVideoId, getYTString(reelPlayerHeaderRenderer.get("reelTitleText")),
					getYTString(reelPlayerHeaderRenderer.get("channelTitleText")));
			return new YouTubeReel(seqCont, video);
		} else if (type == ReelType.WATCH_SEQUENCE) {
			if (!uv.contains("entries") || !uv.get("entries").isArray()) {
				log.error("Failed to find entries!");
				return null;
			}
			final UniJsonValue continuationEndpoint = uv.get("continuationEndpoint");
			final UniJsonValue continuationCommand = continuationEndpoint.get("continuationCommand");
			final String cont = continuationCommand.getString("token");
			final UniJsonValue entries = uv.get("entries");
			final UniJsonValue entrySet = entries.first();
			final YouTubeReel ytr = new YouTubeReel(cont);
			entrySet.stream().forEach((entry) -> {
				final UniJsonValue command = ((UniJsonValue) entry).get("command");
				if (!command.contains("reelWatchEndpoint")) {
					log.error("Failed to find reelWatchEndpoint!");
					return;
				}
				final UniJsonValue reelWatchEndpoint = command.get("reelWatchEndpoint");
				final String nextVideoId = reelWatchEndpoint.getString("videoId");
				final ReelVideo video = new ReelVideo(nextVideoId, null, null); // TODO
				ytr.videos.add(video);
			});
			return ytr;
		} else {
			throw new UnsupportedOperationException("Unknown reel type: " + type);
		}
	}

	public static void main(String[] args) throws Exception {
		final YouTubeReel ytr = extract();
		JVM.println("cont: " + ytr.getContinuation());
		JVM.println("videos: " + ytr.getVideos());
	}
}
